﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Shapes;


namespace Shrestha.Trigraph
{
    class TriGraphCanvas : Canvas, IObserver<WorkingSet>
    {
        #region Private Constants and Variables
        private const double TICKLENGTH = 5.0;
        private const double MARKERDIM = 4.0;

        private double FONTSIZEY, FONTSIZEX, FONTPADDINGX, FONTPADDINGY;
        private double canvasHeight, canvasWidth, px1, px2, px3, px4, py1, py2, py3, py4;
        private double latValueIncrement, lngValueIncrement, timeValueIncrement;
        private double graphHeight, graphWidth;
        private WorkingSet currentSet;
        private MainWindow mainWindow;

        private IDisposable unsubscriber;

        public void setParent(MainWindow m)
        {
            this.mainWindow = m;
        }

        #endregion
        #region Dependency Properties

        public static readonly DependencyProperty hPaddingProperty = DependencyProperty.Register("horizontalPadding", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty vPaddingProperty = DependencyProperty.Register("verticalPadding", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty showlTicks = DependencyProperty.Register("showLatLngTicks", typeof(bool), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty showtTicks = DependencyProperty.Register("showTimeTicks", typeof(bool), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty tTickInterval = DependencyProperty.Register("timeTickInterval", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty latTickInterval = DependencyProperty.Register("latitudeTickInterval", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty lngTickInterval = DependencyProperty.Register("longitudeTickInterval", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty mxLatitude = DependencyProperty.Register("maxLatitude", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty mnLatitude = DependencyProperty.Register("minLatitude", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty mxLongitude = DependencyProperty.Register("maxLongitude", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty mnLongitude = DependencyProperty.Register("minLongitude", typeof(double), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty mxTime = DependencyProperty.Register("maxTime", typeof(DateTime), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(new DateTime(), FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty mnTime = DependencyProperty.Register("minTime", typeof(DateTime), typeof(TriGraphCanvas), new FrameworkPropertyMetadata(new DateTime(), FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

        #endregion

        #region CLR Properties

        [TypeConverter(typeof(LengthConverter))]
        public double maxLatitude
        {
            get { return (double)base.GetValue(mxLatitude); }
            set { base.SetValue(mxLatitude, value); }
        }
        [TypeConverter(typeof(LengthConverter))]
        public double minLatitude
        {
            get { return (double)base.GetValue(mnLatitude); }
            set { base.SetValue(mnLatitude, value); }
        }
        [TypeConverter(typeof(LengthConverter))]
        public double maxLongitude
        {
            get { return (double)base.GetValue(mxLongitude); }
            set { base.SetValue(mxLongitude, value); }
        }
        [TypeConverter(typeof(LengthConverter))]
        public double minLongitude
        {
            get { return (double)base.GetValue(mnLongitude); }
            set { base.SetValue(mnLongitude, value); }
        }
        [TypeConverter(typeof(DateTimeConverter))]
        public DateTime maxTime
        {
            get { return (DateTime)base.GetValue(mxTime); }
            set { base.SetValue(mxTime, value); }
        }
        [TypeConverter(typeof(DateTimeConverter))]
        public DateTime minTime
        {
            get { return (DateTime)base.GetValue(mnTime); }
            set { base.SetValue(mnTime, value); }
        }

        [TypeConverter(typeof(bool))]
        public bool showLatLngTicks
        {
            get { return (bool)base.GetValue(showlTicks); }
            set { base.SetValue(showlTicks, value); }
        }

        [TypeConverter(typeof(bool))]
        public bool showTimeTicks
        {
            get { return (bool)base.GetValue(showtTicks); }
            set { base.SetValue(showtTicks, value); }
        }

        [TypeConverter(typeof(LengthConverter))]
        public double timeTickInterval
        {
            get { return (double)base.GetValue(tTickInterval); }
            set { base.SetValue(tTickInterval, value); }
        }

        [TypeConverter(typeof(LengthConverter))]
        public double latitudeTickInterval
        {
            get { return (double)base.GetValue(latTickInterval); }
            set { base.SetValue(latTickInterval, value); }
        }

        [TypeConverter(typeof(LengthConverter))]
        public double longitudeTickInterval
        {
            get { return (double)base.GetValue(lngTickInterval); }
            set { base.SetValue(lngTickInterval, value); }
        }

        [TypeConverter(typeof(LengthConverter))]
        public double horizontalPadding
        {
            get { return (double)base.GetValue(hPaddingProperty); }
            set { base.SetValue(hPaddingProperty, value); }
        }

        [TypeConverter(typeof(LengthConverter))]
        public double verticalPadding
        {
            get { return (double)base.GetValue(vPaddingProperty); }
            set { base.SetValue(vPaddingProperty, value); }
        }
        #endregion

        public void DrawAxes()
        {
            #region Preprocessing
            InitializeDefault();
            canvasHeight = this.ActualHeight;
            canvasWidth = this.ActualWidth;
 
            double canvasY = 0.0;
            double canvasX = 0.0;

            ///Paddings up, down, right and left
            double upadding, dpadding, rpadding, lpadding;
            
            lpadding = rpadding = horizontalPadding;
            upadding = 5.0;
            dpadding = verticalPadding;
            
            px1 = canvasX + lpadding;
            py1 = canvasY + upadding;

            px2 = canvasX + lpadding;
            py2 = canvasY + (canvasHeight - dpadding);

            px3 = canvasX + (canvasWidth - rpadding);
            py3 = canvasY + (canvasHeight - dpadding);

            px4 = canvasX + (canvasWidth - rpadding);
            py4 = canvasY + upadding;

            Point p1 = new Point(px1, py1);
            Point p2 = new Point(px2, py2);
            Point p3 = new Point(px3, py3);
            Point p4 = new Point(px4, py4);

            PointCollection pc = new PointCollection();
            pc.Add(p1);
            pc.Add(p2);
            pc.Add(p3);
            pc.Add(p4);

            Polyline poly = new Polyline();
            poly.Points = pc;
            poly.StrokeThickness = 1;
            poly.Stroke = Brushes.Black;
            this.Children.Add(poly);

            graphHeight = canvasHeight - (upadding + dpadding);
            graphWidth = canvasWidth - (lpadding + rpadding);
            double tickLength = TICKLENGTH;

            latValueIncrement = maxLatitude - minLatitude;
            lngValueIncrement = maxLongitude - minLongitude;

            TimeSpan tsp = maxTime.Subtract(minTime);
            timeValueIncrement = tsp.TotalDays;

            #endregion
            #region Show Latitude and Longitude Ticks
            if (showLatLngTicks)
            {
                double latIncrement = latitudeTickInterval <= 0.0 ? 10 : latitudeTickInterval;
                double lngIncrement = longitudeTickInterval <= 0.0 ? 10 : longitudeTickInterval;
                
                int numberTicksLatitude = 0;
                int numberTicksLongitude = 0;

                try
                {
                    numberTicksLatitude = Convert.ToInt32(Math.Ceiling(graphHeight / latIncrement));
                    numberTicksLongitude = Convert.ToInt32(Math.Ceiling(graphHeight / lngIncrement));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }

                double latTextIncrement = latValueIncrement / numberTicksLatitude;
                double lngTextIncrement = lngValueIncrement / numberTicksLongitude;

                double tempLatFromX = px2;
                double tempLatFromY = py2;
                double tempLatToX = px2 - tickLength;
                double tempLatToY = py2;

                double tempExactMultiple = (FONTSIZEY + FONTPADDINGY) / latIncrement;
                int tempMultiple = Convert.ToInt32(Math.Ceiling(tempExactMultiple));
                
                for (int i = 0; i < numberTicksLatitude; i++)
                {
                    double tx = FONTPADDINGX;
                    double ty = tempLatFromY - (3*FONTSIZEY/4);

                    if (tempExactMultiple > 0 && ty > verticalPadding)
                    {
                        if ((i + 1) % tempMultiple == 0)
                        {
                            GraphText(tx, ty, String.Format("{0:0.0000}", minLatitude + latTextIncrement * i), this);
                        }
                    }
                    else
                    {
                        GraphText(tx, ty, String.Format("{0:0.0000}", minLatitude + latTextIncrement * i), this);
                    }

                    Line tickLatLine = new Line();
                    tickLatLine.StrokeThickness = 1;
                    tickLatLine.Stroke = Brushes.Black;
                    tickLatLine.X1 = tempLatFromX;
                    tickLatLine.Y1 = tempLatFromY;
                    tickLatLine.X2 = tempLatToX;
                    tickLatLine.Y2 = tempLatToY;
                    this.Children.Add(tickLatLine);

                    tempLatFromY = tempLatToY -= latIncrement;
                }

                double tempLngFromX = px3;
                double tempLngFromY = py3;
                double tempLngToX = px3 + tickLength;
                double tempLngToY = py3;

                for (int i = 0; i < numberTicksLongitude; i++)
                {
                    double tx = tempLngToX + FONTPADDINGX;
                    double ty = tempLngFromY - (3 * FONTSIZEY / 4);

                    if (tempExactMultiple > 0 && ty > verticalPadding)
                    {
                        if ((i + 1) % tempMultiple == 0)
                        {
                            GraphText(tx, ty, String.Format("{0:0.0000}", minLongitude + lngTextIncrement * i), this);
                        }
                    }
                    else
                    {
                        GraphText(tx, ty, String.Format("{0:0.0000}", minLongitude + lngTextIncrement * i), this);
                    }

                    Line tickLngLine = new Line();
                    tickLngLine.X1 = tempLngFromX;
                    tickLngLine.Y1 = tempLngFromY;
                    tickLngLine.X2 = tempLngToX;
                    tickLngLine.Y2 = tempLngToY;
                    tickLngLine.Stroke = Brushes.Black;
                    tickLngLine.StrokeThickness = 1;
                    this.Children.Add(tickLngLine);

                    tempLngFromY = tempLngToY -= lngIncrement;
                }
            }
            #endregion
            #region Show Time Ticks
            if (showTimeTicks)
            {
                double timeIncrement = timeTickInterval <= 0.0 ? 50 : timeTickInterval;
                int numberTicksTime = 0;

                double tempExactMultiple = (FONTSIZEX + FONTPADDINGX) / timeIncrement;
                int tempMultiple = Convert.ToInt32(Math.Ceiling(tempExactMultiple));

                try
                {
                    numberTicksTime = Convert.ToInt32(Math.Ceiling(graphWidth / timeIncrement));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }

                double tempTimeFromX = px2;
                double tempTimeFromY = py2;
                double tempTimeToX = px2;
                double tempTimeToY = py2 + tickLength;

                double timeTextIncrement = timeValueIncrement / numberTicksTime;

                for (int i = 0; i < numberTicksTime; i++)
                {
                    double tx = tempTimeFromX - FONTSIZEX/2;
                    double ty = tempTimeToY;

                    if (tempExactMultiple > 0 && tx > horizontalPadding)
                    {
                        if ((i + 1) % tempMultiple == 0)
                        {
                            TimeSpan ts = new TimeSpan(Convert.ToInt32(Math.Ceiling(timeTextIncrement * i)), 0, 0, 0);
                            //String s = minTime.Add(ts).ToString();
                            String s = minTime.Add(ts).Month.ToString() +"/"+ minTime.Add(ts).Year.ToString();

                            GraphText(tx, ty, s, this);
                        }
                    }
                    else
                    {
                        TimeSpan ts = new TimeSpan(Convert.ToInt32(Math.Ceiling(timeTextIncrement * i)), 0, 0, 0);
                        //String s = minTime.Add(ts).ToString();
                        String s = minTime.Add(ts).Month.ToString() + "/" + minTime.Add(ts).Year.ToString();
                        GraphText(tx, ty, s, this);
                    }

                    Line tickTimeLine = new Line();
                    tickTimeLine.Stroke = Brushes.Black;
                    tickTimeLine.StrokeThickness = 1;
                    tickTimeLine.X1 = tempTimeFromX;
                    tickTimeLine.Y1 = tempTimeFromY;
                    tickTimeLine.X2 = tempTimeToX;
                    tickTimeLine.Y2 = tempTimeToY;
                    this.Children.Add(tickTimeLine);

                    tempTimeFromX = tempTimeToX += timeIncrement;
                }
            }
            #endregion
        }

        private void InitializeDefault()
        {
            FONTSIZEY = FONTSIZEY <= 0.0 ? 10.0 : FONTSIZEY;
            FONTSIZEX = FONTSIZEX <= 0.0 ? 10.0 : FONTSIZEX;
            FONTPADDINGX = FONTPADDINGX <= 0.0 ? 5.0 : FONTPADDINGX;
            FONTPADDINGY = FONTPADDINGY <= 0.0 ? 2.0 : FONTPADDINGY;
        }

        private void GraphText(double x, double y, string text, Canvas c)
        {
            TextBlock textBlock = new TextBlock();
            textBlock.Text = text;
            textBlock.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
            textBlock.Foreground = Brushes.Black;
            textBlock.FontSize = FONTSIZEY;
            Canvas.SetLeft(textBlock, x);
            Canvas.SetTop(textBlock, y);
            
            c.Children.Add(textBlock);
        }

        public void Plot(TriEvent e, bool showLocationLines){
            double templat = ((e.Latitude - minLatitude)/(maxLatitude-minLatitude)) * graphHeight;
            double pty1 = py2- templat;
            double ptx1 = px2;
            
            double templng = ((e.Longitude - minLongitude)/(maxLongitude - minLongitude)) * graphHeight;
            double pty2 = py3 - templng;
            double ptx2 = px3;

            double m = (pty2 - pty1) / graphWidth;
            
            double tempx = (e.EventDate.Subtract(minTime).TotalDays / maxTime.Subtract(minTime).TotalDays) * graphWidth;
            double tempy = m * tempx + pty1;

            double xcoordinate = tempx + ptx1;
            double ycoordinate = tempy;

            //GraphText(xcoordinate, ycoordinate, Convert.ToString(e.Longitude), this);
            //GraphText(xcoordinate, ycoordinate, e.EventDate.Month.ToString() +","+e.EventDate.Year.ToString(), this);

            if (showLocationLines)
            {
                try
                {
                    Line line = new Line
                    {
                        X1 = ptx1,
                        X2 = ptx2,
                        Y1 = pty1,
                        Y2 = pty2,
                        Stroke = Brushes.LightGray,
                        StrokeThickness = 0.8,
                        Opacity = 0.5
                    };
                    this.Children.Add(line);
                }
                catch (Exception someException)
                {
                    Console.WriteLine(someException.Message);
                }
            }

            TriMarker tm = new TriMarker(xcoordinate, ycoordinate, MARKERDIM, MARKERDIM, e, mainWindow);
            
            this.Children.Add(tm);

            //this.Children.Add(rect);
        }

        public void Plot(List<TriEvent> le, bool showLocationLines)
        {
            foreach (TriEvent e in le)
            {
                Plot(e, showLocationLines);
            }
        }

        public void Draw(WorkingSet working)
        {
            this.currentSet = working;
            this.Children.Clear();
            horizontalPadding = 50.0;
            verticalPadding = 50.0;

            showLatLngTicks = true;
            latitudeTickInterval = 50;
            longitudeTickInterval = 50;

            showTimeTicks = true;
            timeTickInterval = 50;

            maxLatitude = currentSet.MaxDbLat;
            minLatitude = currentSet.MinDbLat;

            maxLongitude = currentSet.MaxDbLng;
            minLongitude = currentSet.MinDbLng;

            maxTime = currentSet.MaxDbDate;
            minTime = currentSet.MinDbDate;

            DrawAxes();

            Plot(currentSet.TriEventList, currentSet.ShowLocationLines);
        }

        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        public void OnError(Exception error)
        {
            throw new NotImplementedException();
        }

        public void OnNext(WorkingSet value)
        {
            if (currentSet.CompareTo(value) != 0)
            {
                currentSet = value;
                Draw(value);
            }
        }

        public virtual void Subscribe(IObservable<WorkingSet> provider)
        {
            if (provider != null)
                unsubscriber = provider.Subscribe(this);
        }

        public virtual void Unsubscribe()
        {
            unsubscriber.Dispose();
        }

        public void GetExtents(Point m, Point n, out double minFilteredLat, out double maxFilteredLat, out double minFilteredLng, out double maxFilteredLng, out DateTime maxFilteredTime, out DateTime minFilteredTime)
        {
            double tmx = m.X < px1 ? px1 : m.X;
            double tmy = m.Y < py1 ? py1 : m.Y;
            double tnx = n.X > px3 ? px3 : n.X;
            double tny = n.Y > py3 ? py3 : n.Y;

            //double modifier = ((tmx - px1) / (px3 - px1));
            double modifier = ((tmy - py1) / (py3 - py1));
            minFilteredLat = modifier * latValueIncrement + minLatitude;
            minFilteredLng = modifier * lngValueIncrement + minLongitude;

            modifier = ((tny - py1) / (py3 - py1));
            maxFilteredLat = modifier * latValueIncrement + minLatitude;
            maxFilteredLng = modifier * lngValueIncrement + minLongitude;

            minFilteredTime = minTime.AddDays(((tmx - px1) / (px3 - px1)) * timeValueIncrement);
            maxFilteredTime = minTime.AddDays(((tnx - px1) / (px3 - px1)) * timeValueIncrement);
        }
    }
}
